package com.cbtx.networklib.encrypt

import android.util.Base64
import android.util.Log
import com.cbtx.networklib.exception.CryptoFailedException
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.security.SecureRandom
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
import javax.crypto.Cipher
import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec

/**
 * author : joker.peng
 * e-mail : joker@fiture.com
 * date   : 2021/7/1 9:56 AM
 * desc   : 加密实现
 */
class CryptoServiceImpl : CryptoService {
    companion object {
        const val AES = "AES"
        const val AES_KEY = "2u@H3N*bEJW5pbDa"
        const val AES_CBC = "AES/CBC/PKCS5Padding"
        const val DEFAULT_IV_SIZE = 16
        const val SEPARATE = ":"
        const val CHARSET_NAME = "UTF-8"
    }

    private val sRandom = SecureRandom()

    private var mAesKey: String

    constructor() {
        mAesKey = AES_KEY
    }

    constructor(aesKey: String) {
        mAesKey = aesKey
    }

    @Throws(CryptoFailedException::class)
    override fun decrypt(cipherText: String): String {
        return if (cipherText.isNullOrEmpty()) {
            cipherText
        } else try {
            // 拆分aes向量及密文
            val encryptItems = cipherText.split(SEPARATE.toRegex()).toTypedArray()

            // base64 decode
            val ivBytes = Base64.decode(encryptItems[0], Base64.DEFAULT)
            val aesEncryptText = Base64.decode(encryptItems[1], Base64.DEFAULT)

            // aes解密
            val compressedBytes = aesDecrypt(aesEncryptText, mAesKey!!.toByteArray(), ivBytes)

            // gzip解压
            val out = ByteArrayOutputStream()
            val byteArrayInputStream = ByteArrayInputStream(compressedBytes)
            val gunzip = GZIPInputStream(byteArrayInputStream)
            val buffer = ByteArray(512)
            var n: Int
            while (gunzip.read(buffer).also { n = it } >= 0) {
                out.write(buffer, 0, n)
            }
            gunzip.close()
            out.toString(CHARSET_NAME)
        } catch (e: IOException) {
            Log.e(
                CryptoServiceImpl::class.java.simpleName,
                String.format("Decrypt cipher text failed, due to: %s", e.message)
            )
            throw CryptoFailedException(e)
        }
    }

    @Throws(CryptoFailedException::class)
    override fun encrypt(plainText: String?): String? {
        return if (plainText.isNullOrEmpty()) {
            plainText
        } else try {
            // gzip压缩
            val out = ByteArrayOutputStream()
            val gzip = GZIPOutputStream(out)
            gzip.write(plainText.toByteArray(charset(CHARSET_NAME)))
            gzip.close()

            // 获取 aes 向量
            val vi = generateIV()

            // aes加密
            val encryptByte = aesEncrypt(out.toByteArray(), mAesKey!!.toByteArray(), vi)

            // 13base64 encode
            val encryptContent = Base64.encodeToString(encryptByte, Base64.NO_WRAP)
            val viStr = Base64.encodeToString(vi, Base64.NO_WRAP)
            viStr + SEPARATE + encryptContent
        } catch (e: Exception) {
            Log.e(
                CryptoServiceImpl::class.java.simpleName,
                String.format("Encrypt plain text failed, due to: %s", e.message)
            )
            throw CryptoFailedException(e)
        }
    }

    /**
     * 生成随机向量,默认大小为cipher.getBlockSize(), 16字节.
     */
    private fun generateIV(): ByteArray {
        val bytes = ByteArray(DEFAULT_IV_SIZE)
        sRandom.nextBytes(bytes)
        return bytes
    }

    /**
     * 使用AES加密原始字符串.
     *
     * @param input 原始输入字符数组
     * @param key   符合AES要求的密钥
     */
    @Throws(Exception::class)
    private fun aesEncrypt(input: ByteArray, key: ByteArray): ByteArray? {
        return aes(input, key, Cipher.ENCRYPT_MODE)
    }

    /**
     * 使用AES加密原始字符串.
     *
     * @param input 原始输入字符数组
     * @param key   符合AES要求的密钥
     * @param iv    初始向量
     */
    @Throws(Exception::class)
    private fun aesEncrypt(input: ByteArray, key: ByteArray, iv: ByteArray): ByteArray {
        return aes(input, key, iv, Cipher.ENCRYPT_MODE)
    }

    /**
     * 使用AES解密字符串, 返回原始字符串.
     *
     * @param input Hex编码的加密字符串
     * @param key   符合AES要求的密钥
     */
    @Throws(Exception::class)
    private fun aesDecrypt(input: ByteArray, key: ByteArray): ByteArray? {
        return aes(input, key, Cipher.DECRYPT_MODE)
    }

    /**
     * 使用AES解密字符串, 返回原始字符串.
     *
     * @param input Hex编码的加密字符串
     * @param key   符合AES要求的密钥
     * @param iv    初始向量
     */
    @Throws(Exception::class)
    private fun aesDecrypt(input: ByteArray, key: ByteArray, iv: ByteArray): ByteArray {
        return aes(input, key, iv, Cipher.DECRYPT_MODE)
    }

    /**
     * 使用AES加密或解密无编码的原始字节数组, 返回无编码的字节数组结果.
     *
     * @param input 原始字节数组
     * @param key   符合AES要求的密钥
     * @param mode  Cipher.ENCRYPT_MODE 或 Cipher.DECRYPT_MODE
     */
    @Throws(Exception::class)
    private fun aes(input: ByteArray, key: ByteArray, mode: Int): ByteArray? {
        val secretKey: SecretKey = SecretKeySpec(key, AES)
        val cipher = Cipher.getInstance(AES)
        cipher.init(mode, secretKey)
        return cipher.doFinal(input)
    }

    /**
     * 使用AES加密或解密无编码的原始字节数组, 返回无编码的字节数组结果.
     *
     * @param input 原始字节数组
     * @param key   符合AES要求的密钥
     * @param iv    初始向量
     * @param mode  Cipher.ENCRYPT_MODE 或 Cipher.DECRYPT_MODE
     */
    @Throws(Exception::class)
    private fun aes(input: ByteArray, key: ByteArray, iv: ByteArray, mode: Int): ByteArray {
        val secretKey: SecretKey = SecretKeySpec(key, AES)
        val ivSpec = IvParameterSpec(iv)
        val cipher = Cipher.getInstance(AES_CBC)
        cipher.init(mode, secretKey, ivSpec)
        return cipher.doFinal(input)
    }
}
